﻿  package{
import flash.events.*;
    import org.papervision3d.cameras.*;
    import org.papervision3d.view.Viewport3D;
    import org.papervision3d.objects.DisplayObject3D;
    import org.papervision3d.core.math.*;
    import org.papervision3d.core.log.PaperLogger;

    /**
    * InteractiveCamera3D is a mouse controlled camera.
    */ 
    class InteractiveCamera3D extends Camera3D {

        /**
        * Constructor.
        * 
        * @param   viewport        The viewport, used for mouse interaction
        * @param   fov             This value is the vertical Field Of View (FOV) in degrees.
        * @param   near            Distance to the near clipping plane.
        * @param   far             Distance to the far clipping plane.
        * @param   useCulling      Boolean indicating whether to use frustum culling. When true all objects outside the view will be culled.
        * @param   useProjection   Boolean indicating whether to use a projection matrix for perspective.
        */ 
        public function InteractiveCamera3D(
            viewport3D:Viewport3D, // TODO: stage/viewport??
            value:Boolean=true,
			fov:Number=60,
            near:Number=10,
            far:Number=5000,
            useCulling:Boolean=false,
            useProjection:Boolean=false) {
            super( fov, near, far, useCulling, useProjection );
            this.viewport3D = viewport3D;
				viewport3D.stage.addEventListener( MouseEvent.MOUSE_DOWN, mouseDown );
				
        }
		public function detroyEvent():void{
				viewport3D.stage.removeEventListener( MouseEvent.MOUSE_DOWN, mouseDown );
			}
        protected var targetDistance:Number = 1000;
        protected var viewport3D:Viewport3D;
		
        public override function set target(object:DisplayObject3D):void
        {
            PaperLogger.error("You tried to set an InteractiveCamera3D's target property. " +
                "Don't do that! (you can use lookAt instead)");
        }

        public override function lookAt( targetObject:DisplayObject3D, upAxis:Number3D=null ):void {
            targetDistance = distanceTo( targetObject );
            super.lookAt( targetObject, upAxis );
        }

        public static const SPIN:uint = 1;
        public static const MOVE:uint = 2;
        public static const ZOOM:uint = 3;
        public static const INACTIVE:uint = 0;

        protected var _state:uint = INACTIVE;
        public function get state():uint {
            return _state;
        }
        
        // these hold values from when interaction was started
        protected var initialTransform:Matrix3D;
        protected var initialMouseX:Number;
        protected var initialMouseY:Number;

        protected var enterFrame:Function;
        
        protected function startInteraction( type:uint, listener:Function ):void {
            if( state != INACTIVE ) {
                stopInteraction();
            }
            if( _transformDirty ) {
                updateTransform();
            }
            initialTransform = Matrix3D.clone( this.transform );
            initialMouseX = viewport3D.stage.mouseX;
            initialMouseY = viewport3D.stage.mouseY;
            _state = type;
            enterFrame = listener;
            viewport3D.stage.addEventListener( Event.ENTER_FRAME, enterFrame );
            viewport3D.stage.addEventListener( MouseEvent.MOUSE_UP, stopInteraction );
        }

        protected function stopInteraction( e:Event = null ):void {
            viewport3D.stage.removeEventListener( Event.ENTER_FRAME, enterFrame );
            viewport3D.stage.removeEventListener( MouseEvent.MOUSE_UP, stopInteraction );
            _state = INACTIVE;
        }

        protected function spinHandler( e:Event ):void {
            // calculate axis - perpendicular to mouse vector, swap x and y
            var p:Number3D = new Number3D( 
                (initialMouseY - viewport3D.stage.mouseY) / viewport3D.stage.height * 3,
                (initialMouseX - viewport3D.stage.mouseX) / viewport3D.stage.width * 3,
                0 );
            
            // calculate angle (radians)

            var rad:Number = -p.modulo * Math.PI;
            
            // rotate axis (compensate for cam rotation)
            Matrix3D.rotateAxis( initialTransform, p );

            // create a target object (from targetDistance)
            var target:DisplayObject3D = new DisplayObject3D;
            target.copyTransform( initialTransform );
            target.moveForward( targetDistance );
            var refPoint:Number3D = new Number3D( target.x, target.y, target.z );
            
            // rotate cam around axis
            copyTransform( 
                Matrix3D.multiply( 
                    Matrix3D.rotationMatrixWithReference( p, rad, refPoint ), 
                    initialTransform ) );
        }

        protected function moveHandler( e:Event ):void {
            var x:Number = initialMouseX - viewport3D.stage.mouseX;
            var y:Number = initialMouseY - viewport3D.stage.mouseY;
            copyTransform( initialTransform );
            moveDown( y );
            moveRight( x );
        }

        // TODO: rewrite this. targetdistance is no good without a real target object
        protected function zoomHandler( e:Event ):void {
            var y:Number = (initialMouseY - viewport3D.stage.mouseY) / viewport3D.stage.height;
            var dist:Number = 0;
            dist = targetDistance * y * 10;

            if( targetDistance - dist > focus ) {
                copyTransform( initialTransform );
                moveForward( dist );
            }
        }

        public function interactiveSpin():void {
            startInteraction( SPIN, spinHandler );
        }

        public function interactiveMove():void {
            startInteraction( MOVE, moveHandler );
        }

        public function interactiveZoom():void {
            startInteraction( ZOOM, zoomHandler );
        }

        protected function mouseDown( e:MouseEvent ):void {
            if( e.shiftKey ) {
                interactiveSpin();
            } 
//else if ( e.altKey ) {
//                interactiveMove();
//            }
			else 
			if ( e.ctrlKey ) {
                interactiveZoom();
            }
        }
    }
  }
